package com.gitee.wsl.scheduler.task

import com.gitee.wsl.Lib
import com.gitee.wsl.ioDispatcher
import com.gitee.wsl.scheduler.sample.CoroutineTaskScheduler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

class SyncTask(
    private val scope: CoroutineScope,
    private val ioContext: CoroutineContext = Lib.ioDispatcher,
    private val taskId:String,
    val block: suspend CoroutineScope.() -> Unit
    ){
        private val activeJob = MutableStateFlow<Job?>(null)

        enum class State {
            Idle,
            Running,
        }

        @OptIn(ExperimentalForInheritanceCoroutinesApi::class)
        val state: StateFlow<State>
            get() = object : StateFlow<State> {
            override val replayCache: List<State> get() = listOf(value)
            override val value: State get() = if (activeJob.value == null) State.Idle else State.Running
            override suspend fun collect(collector: FlowCollector<State>): Nothing {
                activeJob.collect {
                    collector.emit(if (it == null) State.Idle else State.Running)
                }
            }
        }

        fun sync(): Job {
            while (true) {
                // If there's an active Job, grab and return it.
                activeJob.value?.let { return it }

                // Create a new job and attempt to install it as the active one.
                val newJob = scope.launch(start = CoroutineStart.LAZY) {
                    try {
                        block()
                    } finally {
                        activeJob.value = null
                    }
                }
                if (activeJob.compareAndSet(null, newJob)) {
                    newJob.start()
                    return newJob
                }

                // Found non-null Job!? Must have raced someone else. Cancel and try again.
                newJob.cancel()
            }
        }
}


internal fun CoroutineScope.syncTask(taskId:String,block: suspend CoroutineScope.() -> Unit):SyncTask{
   return SyncTask(this, ioContext = this.coroutineContext, taskId = taskId, block = block)
}

fun CoroutineScope.syncTask(taskId:String,
                            taskScheduler: CoroutineTaskScheduler,
                            block: suspend CoroutineScope.() -> Unit
):SchedulerTask {
    return taskScheduler.runTask(syncTask(taskId,block))
}

fun CoroutineTaskScheduler.runTask(syncTask: SyncTask): SchedulerTask {
    return newTask(nextId(), syncTask.sync())
}